#include "stdafx.h"
#include "LuaInfo.h"
#include "LuaUtils.h"
#include "LuaContext.h"
#include <assert.h>

namespace LuaDebug {

    scoped_lua::scoped_lua(lua_State *L) {
        init(NULL, L, false);
    }

    scoped_lua::scoped_lua(LuaContext *ctx, lua_State *L, bool withDebug) {
        init(ctx, L, withDebug);
    }

    void scoped_lua::init(LuaContext *ctx, lua_State *L, bool withDebug) {
        if (ctx != NULL) {
            m_isOldEnabled = ctx->IsDebugEnabled();
            ctx->SetDebugEnable(withDebug);
        }

        m_L = L;
        m_ctx = ctx;
        m_top = (L != NULL ? lua_gettop(L) : -1);
    }

    scoped_lua::~scoped_lua() {
        if (m_ctx != NULL) {
            m_ctx->SetDebugEnable(m_isOldEnabled);
        }
    }

    int scoped_lua::check(int n) {
        if (m_top >= 0) {
            assert(m_top + n == lua_gettop(m_L));
        }

        return n;
    }

    /*-----------------------------------------------------------------*/
    LuaStackFrame::LuaStackFrame(const LuaHandle &lua, int level)
        : m_lua(lua), m_level(level) {
    }

    LuaStackFrame::~LuaStackFrame() {
    }


    /*-----------------------------------------------------------------*/
    LuaVar::LuaVar() :
    m_valueType(-1), 
        m_tableIdx(-1), 
        m_hasFields(false)
    {
    }

    LuaVar::~LuaVar()
    {
    }

    LuaVar::LuaVar(const LuaHandle &lua, const std::string &name, int valueIdx)
        : m_lua(lua), m_name(name)
    {
        lua_State *L = lua.GetState();
        m_value = llutil_tostring_for_varvalue(L, valueIdx);
        m_valueType = lua_type(L, valueIdx);
        m_tableIdx = RegisterTable(L, valueIdx);
        m_hasFields = CheckHasFields(L, valueIdx);
    }

    LuaVar::LuaVar(const LuaHandle &lua, const std::string &name,
        const std::string &error)
        : m_lua(lua), m_name(name), m_value(error), m_valueType(LUA_TNONE)
        , m_tableIdx(-1), m_hasFields(false) {
    }

    bool LuaVar::CheckHasFields(lua_State *L, int valueIdx) const {
#if !defined(LLDEBUG_SHOW_STRING_METATABLE)
        if (lua_isstring(L, valueIdx)) {
            return false;
        }
#endif

        // Check whether 'valueIdx' has metatable.
        if (lua_getmetatable(L, valueIdx) != 0) {
            lua_pop(L, 1);
            return true;
        }

        if (!lua_istable(L, valueIdx)) {
            return false;
        }

        // Check whether 'valueIdx' has fields.
        lua_pushnil(L);
        if (lua_next(L, valueIdx) != 0) {
            lua_pop(L, 2);
            return true;
        }

        return false;
    }

    int LuaVar::RegisterTable(lua_State *L, int valueIdx) {
        if (!lua_istable(L, valueIdx)) {
            if (lua_getmetatable(L, valueIdx) == 0) {
                return -1;
            }
            lua_pop(L, 1);
        }

        // OriginalObj couldn't be handled correctly.
        if (lua_islightuserdata(L, valueIdx)
            && lua_topointer(L, valueIdx) == &llutil_address_for_internal_table) {
                return -1;
        }

        int top = lua_gettop(L);

        // Try to do "table = registry[&OriginalObj]"
        lua_pushlightuserdata(L, (void *)&llutil_address_for_internal_table);
        lua_rawget(L, LUA_REGISTRYINDEX);
        if (!lua_istable(L, -1)) {
            lua_pop(L, 1);

            // local newtable = {}
            lua_newtable(L);

            // setmetatable(newtable, {__mode="vk"})
            lua_newtable(L);
            lua_pushliteral(L, "__mode");
            lua_pushliteral(L, "vk");
            lua_rawset(L, -3);
            lua_setmetatable(L, -2);

            // newtable[0] = 1 -- initial index
            lua_pushnumber(L, (lua_Number)1);
            lua_rawseti(L, -2, 0);

            // registry[&OriginalObj] = newtable
            lua_pushlightuserdata(L, (void *)&llutil_address_for_internal_table);
            lua_pushvalue(L, -2);
            lua_rawset(L, LUA_REGISTRYINDEX);
        }

        // table is registry[&OriginalObj]
        int table = lua_gettop(L);
        int n = 0; // use after

        // Check table[valueIdx] is number.
        lua_pushvalue(L, valueIdx);
        lua_rawget(L, table);
        if (lua_isnumber(L, -1)) {
            n = (int)lua_tonumber(L, -1);
            lua_pop(L, 1);

            // Check table[n] is table.
            lua_rawgeti(L, table, n);
            if (lua_istable(L, -1)) {
                // No problems.
                lua_pop(L, 1);
            }
            else {
                lua_pop(L, 1);

                // table[n] = valueIdx, again
                lua_pushvalue(L, valueIdx);
                lua_rawseti(L, table, n);
            }
        }
        else {
            lua_pop(L, 1);

            // n = table[0]
            lua_rawgeti(L, table, 0);
            n = (int)lua_tonumber(L, -1);
            lua_pop(L, 1);

            // table[0] = n + 1
            lua_pushnumber(L, (lua_Number)(n + 1));
            lua_rawseti(L, table, 0);

            // table[valueIdx] = n
            lua_pushvalue(L, valueIdx);
            lua_pushnumber(L, (lua_Number)n);
            lua_rawset(L, table);

            // table[n] = valueIdx
            lua_pushvalue(L, valueIdx);
            lua_rawseti(L, table, n);
        }

        lua_pop(L, 1);
        assert(top == lua_gettop(L));
        (void)top;
        return n;
    }

    int LuaVar::PushTable(lua_State *L) const {
        if (m_tableIdx < 0) {
            return -1;
        }

        // push registry[&OriginalObj][m_tableIdx]
        lua_pushlightuserdata(L, (void *)&llutil_address_for_internal_table);
        lua_rawget(L, LUA_REGISTRYINDEX);
        lua_rawgeti(L, -1, m_tableIdx);
        lua_remove(L, -2);
        return 0;
    }

    bool LuaVar::IsOk() const
    {
        return (m_valueType >= 0);
    }

    const LuaHandle & LuaVar::GetLua() const
    {
        return m_lua;
    }

    const std::string & LuaVar::GetName() const
    {
        return m_name;
    }

    const std::string & LuaVar::GetValue() const
    {
        return m_value;
    }

    int LuaVar::GetValueType() const
    {
        return m_valueType;
    }

    std::string LuaVar::GetValueTypeName() const
    {
        return llutil_get_typename(m_valueType);
    }

    bool LuaVar::HasFields() const
    {
        return m_hasFields;
    }

    /*-----------------------------------------------------------------*/
    LuaBacktrace::LuaBacktrace(const LuaHandle &lua,
        const std::string &name,
        const std::string &sourceKey,
        const std::string &sourceTitle,
        int line, int level)
        : m_lua(lua), m_funcName(name)
        , m_key(sourceKey), m_sourceTitle(sourceTitle)
        , m_line(line), m_level(level) {
    }

    LuaBacktrace::LuaBacktrace() {
    }

    LuaBacktrace::~LuaBacktrace() {
    }

    const LuaHandle & LuaBacktrace::GetLua() const
    {
        return m_lua;
    }

    const std::string & LuaBacktrace::GetFuncName() const
    {
        return m_funcName;
    }

    const std::string & LuaBacktrace::GetKey() const
    {
        return m_key;
    }

    const std::string & LuaBacktrace::GetTitle() const
    {
        return m_sourceTitle;
    }

    int LuaBacktrace::GetLine() const
    {
        return m_line;
    }

    int LuaBacktrace::GetLevel() const
    {
        return m_level;
    }


} // end of namespace LuaDebug
